#include "StompedReality/Core/TypeSystem/TypeField.h"
#include "StompedReality/Core/TypeSystem/TypeInfo.h"
#include "StompedReality/Core/TypeSystem/TypeSystem.h"
#include "StompedReality/Math/Vector2.h"
#include <UnitTest++.h>
#include <iostream>
class FVFClass
{
 public:

  float b;
  StompedReality::Math::Vector2 a;
  float c;

 public:

  FVFClass( )
  {
    a = StompedReality::Math::Vector2 ( 0, 0 );
    b = 0;
    c = 0;
  }

  ~FVFClass( )
  {
  }

};

class FloatTestClass
{
 public:

  float a;
  float b;
  FloatTestClass( )
  {
    a = 0;
    b = 0;
  }

  ~FloatTestClass( )
  {
  }

};

class LTest
{
 public:

  StompedReality::Math::Vector2 vecA;
  StompedReality::Math::Vector2 vecB;
  LTest( )
  {
    this->vecA = StompedReality::Math::Vector2 ( 0, 0 );
    this->vecB = StompedReality::Math::Vector2 ( 0, 0 );
  }

  ~LTest( )
  {
  }

};

class L2Test
{
 public:

  LTest lA;
  LTest lB;
  L2Test( )
  {
  }

  ~L2Test( )
  {

  }

};

struct ListFVFClass
{
  std::vector<FVFClass> list;
};
struct ListObj
{
  std::vector<int> list;
};
SUITE ( Type_System_Test )
{
  TEST ( Field_Constructor_Test )
  {
    StompedReality::type_system::TypeField fieldA ( "a", &FVFClass::a );
    StompedReality::type_system::TypeField fieldB ( "b", &FVFClass::b );
    StompedReality::type_system::TypeField fieldC ( "c", &FVFClass::c );
    CHECK_EQUAL ( StompedReality::hash ( typeid ( StompedReality::Math::Vector2 ).name( ) ),
                  fieldA.type );
    CHECK_EQUAL ( true, sizeof ( float ) <= fieldA.offset );
    CHECK_EQUAL ( StompedReality::hash ( typeid ( float ).name( ) ),
                  fieldB.type );
    CHECK_EQUAL ( 0, fieldB.offset );
    CHECK_EQUAL ( StompedReality::hash ( typeid ( float ).name( ) ),
                  fieldC.type );
    CHECK_EQUAL ( true, fieldC.offset >= ( sizeof ( StompedReality::Math::Vector2 )
                                           + sizeof ( float ) ) );
    CHECK_EQUAL ( true, fieldC.offset <= sizeof ( FVFClass ) );
  }
  TEST ( Info_Constructor_Test )
  {
    StompedReality::type_system::TypeSystem::CreateTypeInfo<FVFClass> ( "TO" )->
    addTypeField ( StompedReality::type_system::TypeField ( "a", &FVFClass::a ) )->
    addTypeField ( StompedReality::type_system::TypeField ( "b", &FVFClass::b ) )->
    addTypeField ( StompedReality::type_system::TypeField ( "c", &FVFClass::c ) );
    StompedReality::type_system::TypeInfo* ttoType =
      StompedReality::type_system::TypeSystem::GetType<FVFClass>( ); //( "TO", fields );
    CHECK_EQUAL ( StompedReality::hash ( typeid ( StompedReality::Math::Vector2 ).name( ) ),
                  ttoType->fields[0].type );
    CHECK_EQUAL ( true, sizeof ( float ) <= ttoType->fields[0].offset );
    CHECK_EQUAL ( StompedReality::hash ( typeid ( float ).name( ) ),
                  ttoType->fields[1].type );
    CHECK_EQUAL ( 0, ttoType->fields[1].offset );
    CHECK_EQUAL ( StompedReality::hash ( typeid ( float ).name( ) ),
                  ttoType->fields[2].type );
    CHECK_EQUAL ( true, ttoType->fields[2].offset >= ( sizeof ( StompedReality::Math::Vector2 )
                                                       + sizeof ( float ) ) );
    CHECK_EQUAL ( true, ttoType->fields[2].offset <= sizeof ( FVFClass ) );
  }
  TEST ( TypeTestObject_write )
  {
    StompedReality::type_system::TypeSystem::CreateTypeInfo<FVFClass> ( "TO" )->
    addTypeField ( StompedReality::type_system::TypeField ( "a", &FVFClass::a ) )->
    addTypeField ( StompedReality::type_system::TypeField ( "b", &FVFClass::b ) )->
    addTypeField ( StompedReality::type_system::TypeField ( "c", &FVFClass::c ) );
    StompedReality::type_system::TypeInfo* ttoType =
      StompedReality::type_system::TypeSystem::GetType<FVFClass>( ); //( "TO", fields );
    //std::cout << "To STR: " << std::endl;
    //std::cout << ttoType->to_s() << std::endl;
    FVFClass testObj;
    testObj.a.SetX ( 3 );
    testObj.a.SetY ( -2.5f );
    testObj.b = -34;
    testObj.c = 112.4f;
    Json::Value root;
    ttoType->write ( root, ( char* ) &testObj );

    //std::cout << root;
    FVFClass checkOBJ;
    ttoType->read ( root, ( char* ) &checkOBJ );
    CHECK_CLOSE ( testObj.a.GetX( ), checkOBJ.a.GetX( ), 0.0001 );
    CHECK_CLOSE ( testObj.a.GetY( ), checkOBJ.a.GetY( ), 0.0001 );
    CHECK_CLOSE ( testObj.b, checkOBJ.b, 0.0001 );
    CHECK_CLOSE ( testObj.c, checkOBJ.c, 0.0001 );
  }
  TEST ( Float_Writer_Test )
  {
    StompedReality::type_system::TypeSystem::CreateTypeInfo<FloatTestClass> ( "WRTest" )->
    addTypeField ( StompedReality::type_system::TypeField ( "a", &FloatTestClass::a ) )->
    addTypeField ( StompedReality::type_system::TypeField ( "b", &FloatTestClass::b ) );
    FloatTestClass x;
    x.a = 15;
    x.b = -3;
    Json::Value root;
    StompedReality::type_system::TypeInfo* wrTestType =
      StompedReality::type_system::TypeSystem::GetType<FloatTestClass>( );

    //std::cout << wrTestType->to_s() << std::endl;
    wrTestType->write ( root, ( char* ) &x );

    //std::cout << root;
    FloatTestClass y;
    wrTestType->read ( root, ( char* ) &y );
    CHECK_CLOSE ( x.a, y.a, 0.0001 );
    CHECK_CLOSE ( x.b, y.b, 0.0001 );
  }

  TEST ( LTest_Test )
  {
    StompedReality::type_system::TypeSystem::CreateTypeInfo<LTest> ( "Layer" )->
    addTypeField ( StompedReality::type_system::TypeField ( "translation", &LTest::vecA ) )->
    addTypeField ( StompedReality::type_system::TypeField ( "speed", &LTest::vecB ) );
    StompedReality::type_system::TypeInfo* lTestType =
      StompedReality::type_system::TypeSystem::GetType<LTest>( );
    LTest lTestA;
    lTestA.vecA.SetX ( 1 );
    lTestA.vecA.SetY ( 2 );
    lTestA.vecB.SetX ( 3 );
    lTestA.vecB.SetY ( 4 );
    Json::Value root;
    lTestType->write ( root, ( char* ) &lTestA );
    LTest lTestB;
    lTestType->read ( root, ( char* ) &lTestB );
    CHECK_CLOSE ( lTestA.vecA.GetX( ), lTestB.vecA.GetX( ), 0.0001 );
    CHECK_CLOSE ( lTestA.vecA.GetY( ), lTestB.vecA.GetY( ), 0.0001 );
    CHECK_CLOSE ( lTestA.vecB.GetX( ), lTestB.vecB.GetX( ), 0.0001 );
    CHECK_CLOSE ( lTestA.vecB.GetY( ), lTestB.vecB.GetY( ), 0.0001 );
  }
  TEST ( List_Test )
  {
    StompedReality::type_system::TypeSystem::CreateTypeInfo<ListObj> ( "ListObj" )->
    addTypeField ( StompedReality::type_system::TypeField ( "list", &ListObj::list ) );
    StompedReality::type_system::TypeInfo* listObjType =
      StompedReality::type_system::TypeSystem::GetType<ListObj>( );

    //std::cout << listObjType->to_s() << std::endl;

    ListObj obj;
    obj.list.push_back ( 1 );
    obj.list.push_back ( 37 );
    obj.list.push_back ( 4 );
    obj.list.push_back ( 234 );
    obj.list.push_back ( 56 );
    Json::Value root;
    listObjType->write ( root, ( char* ) &obj );

    //std::cout << root;
    ListObj objB;
    listObjType->read ( root, ( char* ) &objB );
    CHECK_EQUAL ( obj.list.size( ), objB.list.size( ) );
    CHECK_EQUAL ( obj.list[0], objB.list[0] );
    CHECK_EQUAL ( obj.list[1], objB.list[1] );
    CHECK_EQUAL ( obj.list[2], objB.list[2] );
    CHECK_EQUAL ( obj.list[3], objB.list[3] );
    CHECK_EQUAL ( obj.list[4], objB.list[4] );
  }
  TEST ( List_Struct_Test )
  {
    StompedReality::type_system::TypeSystem::CreateTypeInfo<ListFVFClass> ( "ListFVFClass" )->
    addTypeField ( StompedReality::type_system::TypeField ( "list", &ListFVFClass::list ) );
    StompedReality::type_system::TypeInfo* listObjType =
      StompedReality::type_system::TypeSystem::GetType<ListFVFClass>( );

    //	std::cout << listObjType->to_s() << std::endl;
    ListFVFClass obj;
    FVFClass itemA;
    itemA.a = StompedReality::Math::Vector2 ( 5, 9 );
    itemA.b = 0.5f;
    itemA.c = -17.4f;
    FVFClass itemB;
    itemB.a = StompedReality::Math::Vector2 ( 15, -9 );
    itemB.b = 123.2f;
    itemB.c = 37.5f;
    obj.list.push_back ( itemA );
    obj.list.push_back ( itemB );
    Json::Value root;
    listObjType->write ( root, ( char* ) &obj );

    //std::cout << root;
    ListFVFClass objB;
    listObjType->read ( root, ( char* ) &objB );
    CHECK_EQUAL ( obj.list.size( ), objB.list.size( ) );
    CHECK_CLOSE ( obj.list[0].a.GetX( ), objB.list[0].a.GetX( ), 0.00001 );
    CHECK_CLOSE ( obj.list[0].a.GetY( ), objB.list[0].a.GetY( ), 0.00001 );
    CHECK_CLOSE ( obj.list[0].b, objB.list[0].b, 0.00001 );
    CHECK_CLOSE ( obj.list[0].c, objB.list[0].c, 0.00001 );
    CHECK_CLOSE ( obj.list[1].a.GetX( ), objB.list[1].a.GetX( ), 0.00001 );
    CHECK_CLOSE ( obj.list[1].a.GetY( ), objB.list[1].a.GetY( ), 0.00001 );
    CHECK_CLOSE ( obj.list[1].b, objB.list[1].b, 0.00001 );
    CHECK_CLOSE ( obj.list[1].c, objB.list[1].c, 0.00001 );
    std::cout << std::endl;
  }
}